package com.ds;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

public class Main {


    private ClassLoader classLoader;

    private static boolean useDefExt = true;

    public static final String TASK_DEFAULT_CLASS = "com.ds.ESDStudio";

    private final Set<File> extensions = new LinkedHashSet<File>();

    private final Set<File> classPath = new LinkedHashSet<File>();

    public void addExtensionDirectory(File directory) {
        extensions.add(directory);
    }

    public void addClassPathList(String fileList) {
        if (fileList != null && fileList.length() > 0) {
            StringTokenizer tokenizer = new StringTokenizer(fileList, File.pathSeparator);
            while (tokenizer.hasMoreTokens()) {
                addClassPath(new File(tokenizer.nextToken()));
            }
        }
    }

    public void addClassPath(File classpath) {
        classPath.add(classpath);
    }


    public static void main(String[] args) {

        // Create the tmpdir if it does not exist yet..
        File tmpdir = new File(System.getProperty("java.io.tmpdir"));
        if (!tmpdir.exists()) {
            tmpdir.mkdirs();
        }

        Main app = new Main();

        List<String> tokens = new LinkedList<String>(Arrays.asList(args));

        app.parseExtensions(tokens);

        File baseLibDir = null;
        try {
            String runningURL = (new URL(Main.class.getProtectionDomain()
                    .getCodeSource().getLocation(), ".")).openConnection()
                    .getPermission().getName();
            File dirfile = new File(runningURL);
            baseLibDir = new File(dirfile, "../lib");

            File  otherLibDir = new File(dirfile, "../../lib");
            app.addExtensionDirectory(otherLibDir);
            System.out.println("baseLibDir=" + baseLibDir.getAbsolutePath());
            File classesDir = new File(dirfile, "classes");
            System.out.println("classesDir=" + classesDir.getAbsolutePath());

            app.addClassPath(classesDir);
            app.addExtensionDirectory(baseLibDir);

        } catch (IOException e) {
            e.printStackTrace();
        }

        try {

            int ret = app.runTaskClass(tokens);
            //    System.exit(ret);
        } catch (ClassNotFoundException e) {
            System.out.println("Could not load class: " + e.getMessage());
            try {
                ClassLoader cl = app.getClassLoader();
                if (cl != null) {
                    System.out.println("Class loader setup: ");
                    printClassLoaderTree(cl);
                }
            } catch (MalformedURLException e1) {
            }
            System.exit(1);
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("Failed to execute main task. Reason: " + e);
            System.exit(1);
        }
    }

    private static int printClassLoaderTree(ClassLoader cl) {
        int depth = 0;
        if (cl.getParent() != null) {
            depth = printClassLoaderTree(cl.getParent()) + 1;
        }

        StringBuffer indent = new StringBuffer();
        for (int i = 0; i < depth; i++) {
            indent.append("  ");
        }

        if (cl instanceof URLClassLoader) {
            URLClassLoader ucl = (URLClassLoader) cl;
            System.out.println(indent + cl.getClass().getName() + " {");
            URL[] urls = ucl.getURLs();
            for (int i = 0; i < urls.length; i++) {
                System.out.println(indent + "  " + urls[i]);
            }
            System.out.println(indent + "}");
        } else {
            System.out.println(indent + cl.getClass().getName());
        }
        return depth;
    }

    public void parseExtensions(List<String> tokens) {
        if (tokens.isEmpty()) {
            return;
        }

        int count = tokens.size();
        int i = 0;

        // Parse for all --extdir and --noDefExt options
        while (i < count) {
            String token = tokens.get(i);
            // If token is an extension dir option
            if (token.equals("--extdir")) {
                // Process token
                count--;
                tokens.remove(i);

                // If no extension directory is specified, or next token is
                // another option
                if (i >= count || tokens.get(i).startsWith("-")) {
                    System.out.println("Extension directory not specified.");
                    System.out.println("Ignoring extension directory option.");
                    continue;
                }

                // Process extension dir token
                count--;
                File extDir = new File(tokens.remove(i));

                if (!canUseExtdir()) {
                    System.out.println("Extension directory feature not available due to the system classpath being able to load: " + TASK_DEFAULT_CLASS);
                    System.out.println("Ignoring extension directory option.");
                    continue;
                }

                if (!extDir.isDirectory()) {
                    System.out.println("Extension directory specified is not valid directory: " + extDir);
                    System.out.println("Ignoring extension directory option.");
                    continue;
                }

                addExtensionDirectory(extDir);
            } else if (token.equals("--noDefExt")) { // If token is
                // --noDefExt option
                count--;
                tokens.remove(i);
                useDefExt = false;
            } else {
                i++;
            }
        }

    }

    public boolean canUseExtdir() {
        try {
            Main.class.getClassLoader().loadClass(TASK_DEFAULT_CLASS);
            return false;
        } catch (ClassNotFoundException e) {
            return true;
        }
    }

    public ClassLoader getClassLoader() throws MalformedURLException {
        if (classLoader == null) {
            // Setup the ClassLoader
            classLoader = Main.class.getClassLoader();
            if (!extensions.isEmpty() || !classPath.isEmpty()) {

                ArrayList<URL> urls = new ArrayList<URL>();
                for (Iterator<File> iter = classPath.iterator(); iter.hasNext(); ) {
                    File dir = iter.next();
                    urls.add(dir.toURI().toURL());
                }

                for (Iterator<File> iter = extensions.iterator(); iter.hasNext(); ) {
                    File dir = iter.next();
                    if (dir.isDirectory()) {
                        File[] files = dir.listFiles();
                        if (files != null) {

                            // Sort the jars so that classpath built is consistently in the same
                            // order. Also allows us to use jar names to control classpath order.
                            Arrays.sort(files, new Comparator<File>() {
                                @Override
                                public int compare(File f1, File f2) {
                                    return f1.getName().compareTo(f2.getName());
                                }
                            });

                            for (int j = 0; j < files.length; j++) {
                                if (files[j].getName().endsWith(".zip") || files[j].getName().endsWith(".jar")) {
                                    urls.add(files[j].toURI().toURL());
                                }
                            }
                        }
                        StringBuffer cmds = new StringBuffer("Java -jar -Xbootclasspath/a: ");
                        for (File file : files) {
                            cmds.append("lib/" + file.getName() + ";");
                        }
                        System.out.println(cmds.toString());

                    }

                }

                URL u[] = new URL[urls.size()];
                urls.toArray(u);
                System.out.println(urls);
                classLoader = new URLClassLoader(u, classLoader);

            }
            Thread.currentThread().setContextClassLoader(classLoader);
        }
        return classLoader;
    }


    public int runTaskClass(List<String> tokens) throws Throwable {

        StringBuilder buffer = new StringBuilder();
        buffer.append(System.getProperty("java.vendor"));
        buffer.append(" ");
        buffer.append(System.getProperty("java.version"));
        buffer.append(" ");
        buffer.append(System.getProperty("java.home"));
        System.out.println("Java Runtime: " + buffer.toString());

        buffer = new StringBuilder();
        buffer.append("current=");
        buffer.append(Runtime.getRuntime().totalMemory() / 1024L);
        buffer.append("k  free=");
        buffer.append(Runtime.getRuntime().freeMemory() / 1024L);
        buffer.append("k  max=");
        buffer.append(Runtime.getRuntime().maxMemory() / 1024L);
        buffer.append("k");
        System.out.println("  Heap sizes: " + buffer.toString());

        List<?> jvmArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
        buffer = new StringBuilder();
        for (Object arg : jvmArgs) {
            buffer.append(" ").append(arg);
        }
        System.out.println("    JVM args:" + buffer.toString());


        ClassLoader cl = getClassLoader();

        Thread.currentThread().setContextClassLoader(cl);


        // Use reflection to run the task.
        try {
            String[] args = tokens.toArray(new String[tokens.size()]);

            Class<?> task = cl.loadClass(TASK_DEFAULT_CLASS);
            task.getClassLoader();

            //  Thread.sleep(10000000);

            Method runTask = task.getMethod("main", new Class[]{
                    String[].class, InputStream.class, PrintStream.class
            });
            return (int) runTask.invoke(task.newInstance(), args, System.in, System.out);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        }
    }

}
